package org.anachronos.jeep.ui.trace;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.anachronos.jeep.model.trace.Class;
import org.anachronos.jeep.model.trace.Invocation;
import org.anachronos.jeep.model.trace.Method;
import org.anachronos.jeep.model.trace.MethodInvocation;
import org.anachronos.jeep.model.trace.ThreadCallStack;
import org.anachronos.jeep.model.trace.SqlStatementExecution;
import org.anachronos.jeep.model.trace.util.BacktraceMapBuilder.KeyGenerator;
import org.anachronos.jeep.ui.trace.util.MethodSignatureGenerator;
import org.anachronos.jeep.ui.trace.util.SqlStatementStyler;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.zest.core.viewers.GraphViewer;
import org.eclipse.zest.core.viewers.IGraphEntityContentProvider;
import org.eclipse.zest.core.viewers.ISelfStyleProvider;
import org.eclipse.zest.core.widgets.GraphConnection;
import org.eclipse.zest.core.widgets.GraphNode;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.algorithms.AbstractLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.HorizontalTreeLayoutAlgorithm;

public class BacktracePage extends TraceBasePage {
	private class BackTraceContentProvider implements IGraphEntityContentProvider {
		private final KeyGenerator keyGenerator = new KeyGenerator();
		private Invocation input;
		private final Set<Invocation> allInvocations = new HashSet<Invocation>();

		@Override
		public void dispose() {
		}

		@Override
		public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) {
			allInvocations.clear();
			this.input = (Invocation) newInput;
			allInvocations.add(input);
			final List<Invocation> traces = getBackTraces();
			for (Invocation invocation : traces) {
				if (!(invocation instanceof ThreadCallStack)) {
					allInvocations.add(invocation);
					while (!(invocation.eContainer() instanceof ThreadCallStack)) {
						invocation = (Invocation) invocation.eContainer();
						allInvocations.add(invocation);
					}
				}
			}
		}

		@Override
		public Object[] getConnectedTo(final Object element) {
			if (element == input) {
				final List<Invocation> backTraces = getBackTraces();
				return backTraces == null ? null : backTraces.toArray();
			} else {
				final Invocation invocation = (Invocation) element;
				final EObject container = invocation.eContainer();
				final boolean lastBackTrace = container == null 
					|| container instanceof ThreadCallStack;
				return lastBackTrace ? null : new Object[] { container };
			}
		}

		private List<Invocation> getBackTraces() {
			final String key = keyGenerator.doSwitch(input);
			final List<Invocation> backTraces = backTraceMap.get(key);
			return backTraces;
		}

		@Override
		public Object[] getElements(final Object input) {
			return allInvocations.toArray();
		}
	}
	
	private class BackTraceLabelProvider extends LabelProvider implements ISelfStyleProvider {
		private final KeyGenerator textGenerator = new KeyGenerator();
		
		@Override
		public String getText(final Object element) {
			if (element instanceof MethodInvocation) {
				final MethodInvocation methodInvocation = (MethodInvocation) element;
				final Method method = methodInvocation.getMethod();
				final String methodSignature = MethodSignatureGenerator.createMethodSignature(method);
				final Class clazz = (Class) method.eContainer();
				return methodSignature + "\n" + clazz.getName();
			}
			if (element instanceof SqlStatementExecution) {
				final SqlStatementExecution sqlStatementExecution = (SqlStatementExecution) element;
				final StyledString styledStatement = SqlStatementStyler.getStyledStatement(sqlStatementExecution);
				return styledStatement.getString();
			}
			if (element instanceof Invocation) {
				final Invocation invocation = (Invocation) element;
				return textGenerator.doSwitch(invocation);
			} else {
				return null;
			}
		}
		@Override
		public void selfStyleConnection(Object element,
				GraphConnection connection) {
			connection.setLineWidth(2);
			connection.setLineColor(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
			PolylineConnection polylineConnection = 
				(PolylineConnection) connection.getConnectionFigure();
			final PolygonDecoration srcDecoration = new PolygonDecoration();
			srcDecoration.setAntialias(SWT.ON);
			polylineConnection.setSourceDecoration(srcDecoration);
			polylineConnection.setTargetDecoration(null);
			polylineConnection.setAntialias(SWT.ON);
		}
		
		@Override
		public void selfStyleNode(Object element, GraphNode node) {
		}
	}
	
	private final Map<String, List<Invocation>> backTraceMap;
	private GraphViewer graphViewer;
	private Object input;
	
	public BacktracePage(final FormEditor editor, final String id, final String title, 
			final Map<String, List<Invocation>> backTraceMap) {
		super(editor, id, title);
		this.backTraceMap = backTraceMap;
	}
	
	public void setInput(final Object input) {
		if (graphViewer != null) {
			graphViewer.setInput(input);
			graphViewer.applyLayout();
		}
		this.input = input;
	}
	
	protected void createFormContent(final IManagedForm managedForm) {
		final GridDataFactory gridDataFactory = 
			GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL).grab(true, true);
		
		final Composite body = managedForm.getForm().getBody();
		body.setLayout(new GridLayout(2, false));
		body.setLayoutData(gridDataFactory.create());
		
		graphViewer = new GraphViewer(body, SWT.NONE);
		graphViewer.getGraphControl().setLayoutData(gridDataFactory.create());
		final AbstractLayoutAlgorithm layoutAlgorithm =			
					new HorizontalTreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);

		graphViewer.setLayoutAlgorithm(layoutAlgorithm, false);
		graphViewer.setContentProvider(new BackTraceContentProvider());
		graphViewer.setLabelProvider(new BackTraceLabelProvider());
		graphViewer.setInput(input);
		
		registerContextMenu(graphViewer);
	}

	@Override
	public Viewer getViewer() {
		return graphViewer;
	}
}
